package org.trc.traffic.analysis;

import java.io.File;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import javax.swing.JFileChooser;

import org.geotools.data.DataUtilities;
import org.geotools.data.DefaultTransaction;
import org.geotools.data.Transaction;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.data.shapefile.ShapefileDataStoreFactory;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.data.simple.SimpleFeatureStore;
import org.geotools.feature.FeatureCollections;
import org.geotools.feature.SchemaException;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.referencing.CRS;
import org.geotools.swing.data.JFileDataStoreChooser;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;

import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.io.WKTReader;

public class AggRegionCongestionClustering {
	double timeCost = 0;

	//输入参数
	protected float minSpatialThreshold;

	protected float minTimeThreshold;
	
	protected int minNum;
	
	//聚簇结果
	protected Map<String, AggRegionCongestionCluster> congestionClusterSet;

	//聚簇
	public void buildAggCongestionCluster(String regionID, ArrayList <RegionCongestionCluster> allRegionClusterList, float minSpatialThreshold, float minTimeThreshold, int minNum) {

		timeCost = System.currentTimeMillis();
		
        // 数据集初始化
		this.minSpatialThreshold = minSpatialThreshold;

		this.minTimeThreshold = minTimeThreshold;
		
		this.minNum = minNum;

		// LinkCongestionCluster分组

		int clusterNum = 1;
		
		for (RegionCongestionCluster regionCC : allRegionClusterList) {
			if (!regionCC.clusterID.startsWith("AggRegion")) {// 未分组
				if(this.expandAggCongestionCluster(regionCC, allRegionClusterList, regionID, clusterNum))
					clusterNum++;
				
				
			}
		}

		// 生成 AggLinkCongestionCluster
		this.congestionClusterSet = Collections.synchronizedMap(new HashMap<String, AggRegionCongestionCluster>());
		
		AggRegionCongestionCluster congestionCluster = null;

		for (RegionCongestionCluster regionCC : allRegionClusterList) {
			String clusterID = regionCC.clusterID;
			if (!regionCC.clusterID.startsWith("AggRegion"))
				continue;
			if (!congestionClusterSet.containsKey(clusterID)) {
				congestionCluster = new AggRegionCongestionCluster(clusterID);
				
				System.out.println(clusterID);
				
				congestionCluster.add(regionCC, regionCC.MBR);
				congestionClusterSet.put(clusterID, congestionCluster);
			} else {
				congestionCluster = congestionClusterSet.get(clusterID);
			    congestionCluster.add(regionCC, regionCC.MBR);
			}
		}
		
		timeCost = System.currentTimeMillis() - this.timeCost;

	}

	private boolean expandAggCongestionCluster(RegionCongestionCluster coreRegionCluster, ArrayList<RegionCongestionCluster> allRegionClusterList,
			String regionID, int clusterNum) {	
		
		System.out.println(clusterNum);
		
		List<RegionCongestionCluster> resultDataSet = this.STRangeQuery(coreRegionCluster, allRegionClusterList);

		if (resultDataSet.size() <  minNum)
			return false;

		coreRegionCluster.clusterID = "AggRegion_" + regionID + "_" + Integer.toString(clusterNum);
		
		for (RegionCongestionCluster regionCC : resultDataSet) {// 标记簇ID
			if (!regionCC.clusterID.startsWith("AggRegion")){
				regionCC.clusterID = "AggRegion_" + regionID + "_" +  Integer.toString(clusterNum);
				expandAggCongestionCluster(regionCC, allRegionClusterList, regionID, clusterNum);			
			}
		}
		
		return true;
	}

	//时空邻域
	private List<RegionCongestionCluster> STRangeQuery(RegionCongestionCluster coreRegionCluster, ArrayList<RegionCongestionCluster> allRegionClusterList) {
		Vector<RegionCongestionCluster> stRange_List = new Vector<RegionCongestionCluster>();

		for (RegionCongestionCluster regionCC : allRegionClusterList) {
			if (regionCC.equals(coreRegionCluster))
				continue;
			if (timeWithin(regionCC, coreRegionCluster)&& spatialWithin(regionCC, coreRegionCluster))
				stRange_List.add(regionCC);
		}

		return stRange_List;
	}

	//空间相似Jarcard计算
	private boolean spatialWithin(RegionCongestionCluster regionCC1, RegionCongestionCluster regionCC2) {		
		if(!regionCC1.MBR.intersects(regionCC2.MBR))
			return false;
				
		float intersection =  (float) regionCC1.MBR.intersection(regionCC2.MBR).getArea();
		
		float union =  (float) (regionCC1.MBR.getArea() + regionCC2.MBR.getArea() - intersection);
		
		float spatialSimilarity = intersection/union;
		
		if(spatialSimilarity > this.minSpatialThreshold)
			return true;
		else
			return false;	
	}

	//时间相似Jarcard计算
	private boolean timeWithin(RegionCongestionCluster regionCC1, RegionCongestionCluster regionCC2) {

		float union =  Math.max(regionCC1.endTime, regionCC2.endTime) - Math.min(regionCC1.startTime, regionCC2.startTime) + 1;
		
		float intersection =  Math.min(regionCC1.endTime, regionCC2.endTime) - Math.max(regionCC1.startTime, regionCC2.startTime) + 1;
		
		if(intersection <= 0)
			return false;
		
		float timeSimilarity = intersection/union;
		
		if(timeSimilarity > this.minTimeThreshold)
			return true;
		else
			return false;	
	}
	
	public ArrayList <AggCongestionCluster> getCongestionCluster() {
		return new ArrayList <AggCongestionCluster> (congestionClusterSet.values());
	}
	
	//----------------------------------------------可视化输出----------------------------------------------------------//
	//获取多边形
	public void getVIPCongestionClusterMap(int minMLinkNum, int minDur) throws Exception {
		//指定SHAPE文件
		JFileDataStoreChooser chooser = new JFileDataStoreChooser("shp");
		chooser.setDialogTitle("Save a new shapefile for muliple congestion clusters");
		chooser.setSaveFile(new File("D:\\VIPCongestionClusterRegionMap.shp"));

		if (chooser.showSaveDialog(null) != JFileChooser.APPROVE_OPTION) {
			return;
		}

		File outputShapefile = chooser.getSelectedFile();
		
        //指定SHAPE类型
		ShapefileDataStoreFactory dataStoreFactory = new ShapefileDataStoreFactory();
		Map<String, Serializable> params = null;
		ShapefileDataStore writeDataStore = null;
		SimpleFeatureType TYPE = null;

		try {
			params = new HashMap<String, Serializable>();
			params.put("url", outputShapefile.toURI().toURL());
			params.put("create spatial index", Boolean.TRUE);

			writeDataStore = (ShapefileDataStore) dataStoreFactory.createNewDataStore(params);
			TYPE = DataUtilities.createType("CongestionRegion",
					"GEOM:Polygon:srid=32650," 
			        + "ID:String,"
					+ "STIME:int," 
			        + "ETIME:int,"
					+ "DUR:int,"
			        + "MLNUM:int"
					);

			writeDataStore.createSchema(TYPE);
			writeDataStore.forceSchemaCRS(CRS.decode("EPSG:32650", true));

		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (SchemaException e) {
			e.printStackTrace();
		}

		// prepare new feature collection
		SimpleFeatureCollection collection = FeatureCollections.newCollection();
		SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(TYPE);
		SimpleFeature feature = null;
		MultiLineString line = null;

		AggRegionCongestionCluster congestionCluster = null;

		String[] clusterIDs = new String[congestionClusterSet.size()];
		congestionClusterSet.keySet().toArray(clusterIDs);

		for (String clusterID : clusterIDs) {
			congestionCluster = (AggRegionCongestionCluster)congestionClusterSet.get(clusterID);
			int mlinkNum = congestionCluster.mLinkTrafficSet.keySet().size();
			int dur = congestionCluster.endTime - congestionCluster.startTime + 1;
			if (mlinkNum >= minMLinkNum && dur >= minDur) {
				int sTime = congestionCluster.startTime;
				int eTime = congestionCluster.endTime;
				Polygon mbr = this.getMBRPolygon(congestionCluster.MBR);
				
				// create feature
				featureBuilder.add(mbr);
				featureBuilder.add(clusterID);
				featureBuilder.add(sTime);
				featureBuilder.add(eTime);
				featureBuilder.add(dur);
				featureBuilder.add(mlinkNum);
				feature = featureBuilder.buildFeature(null);
				collection.add(feature);
			}
		}

		// create transaction
		Transaction transaction = new DefaultTransaction("create");
		String typeName = writeDataStore.getTypeNames()[0];
		SimpleFeatureSource featureSource = writeDataStore.getFeatureSource(typeName);

		// create shape file
		if (featureSource instanceof SimpleFeatureStore) {
			SimpleFeatureStore featureStore = (SimpleFeatureStore) featureSource;
			featureStore.setTransaction(transaction);
			try {
				featureStore.addFeatures(collection);
				transaction.commit();
			} catch (Exception problem) {
				problem.printStackTrace();
				transaction.rollback();
			} finally {
				transaction.close();
			}
			System.exit(0); // success!
		} else {
			System.out
					.println(typeName + " does not support read/write access");
		}

	}
	
	private Polygon getMBRPolygon(Envelope mbr) throws com.vividsolutions.jts.io.ParseException{	
	    GeometryFactory factory = new GeometryFactory();
	    WKTReader wktRdr = new WKTReader(factory);
		
		double minX = mbr.getMinX();
		double minY = mbr.getMinY();
		double maxX = mbr.getMaxX();
		double maxY = mbr.getMaxY();

	    String coords = minX + " " + minY + ", " + maxX + " " + minY+ ", " + maxX+ " " + maxY+ ", " + minX+ " " + maxY+ ", " + minX+ " " + minY ;
	    
	    String wktA = "POLYGON((" + coords + "))";
	    
		return (Polygon) wktRdr.read(wktA);
	}
	
	//----------------------------------------------------------------------------------------------------------------
    @SuppressWarnings("unchecked")
	public static void main(String[] args) throws ParseException{
		SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-mm-dd");
		
		ArrayList <RegionCongestionCluster> allRegionClusterList = new ArrayList <RegionCongestionCluster> ();
		
		STRegionCongestionClustering stRegionCongestionClustering = new STRegionCongestionClustering();
		
		//------------------------------------------------工作日--------------------------------------------------
/*		stRegionCongestionClustering.buildCongestionCluster("Network",  17, 18, dateFormat.parse("2009-3-8"), 300, 1);
			
		allRegionClusterList.addAll((Collection<? extends RegionCongestionCluster>) stRegionCongestionClustering.getVIPCongestionCluster(2, 6, 500));
					
		System.out.println("2009-3-8");
		
		stRegionCongestionClustering.buildCongestionCluster("Network",  17, 18, dateFormat.parse("2009-3-9"), 300, 1);
		
		allRegionClusterList.addAll((Collection<? extends RegionCongestionCluster>) stRegionCongestionClustering.getVIPCongestionCluster(2, 6, 500));
		
		System.out.println("2009-3-9");
		
		stRegionCongestionClustering.buildCongestionCluster("Network",  17, 18, dateFormat.parse("2009-3-10"), 300, 1);
		
		allRegionClusterList.addAll((Collection<? extends RegionCongestionCluster>) stRegionCongestionClustering.getVIPCongestionCluster(2, 6, 500));
		
		System.out.println("2009-3-10");
		
		stRegionCongestionClustering.buildCongestionCluster("Network",  17, 18, dateFormat.parse("2009-3-11"), 300, 1);
		
		allRegionClusterList.addAll((Collection<? extends RegionCongestionCluster>) stRegionCongestionClustering.getVIPCongestionCluster(2, 6, 500));
		
		System.out.println("2009-3-11");
		
		stRegionCongestionClustering.buildCongestionCluster("Network",  17, 18, dateFormat.parse("2009-3-12"), 300, 1);
		
		allRegionClusterList.addAll((Collection<? extends RegionCongestionCluster>) stRegionCongestionClustering.getVIPCongestionCluster(2, 6, 500));
		
		System.out.println("2009-3-12");*/

		//-------------------------------------------周末-------------------------------------
		stRegionCongestionClustering.buildCongestionCluster("Network", 10, 17, dateFormat.parse("2009-3-13"), 300, 1);
		
		allRegionClusterList.addAll((Collection<? extends RegionCongestionCluster>) stRegionCongestionClustering.getVIPCongestionCluster(2, 6, 500));

		stRegionCongestionClustering.buildCongestionCluster("Network", 10, 17, dateFormat.parse("2009-3-14"), 300, 1);
		
		allRegionClusterList.addAll((Collection<? extends RegionCongestionCluster>) stRegionCongestionClustering.getVIPCongestionCluster(2, 6, 500));
		
		//------------------------------------------------------------------------------------

		AggRegionCongestionClustering aggRegionCongestionClustering = new AggRegionCongestionClustering();
		
		aggRegionCongestionClustering.buildAggCongestionCluster("wuchang", allRegionClusterList, 0.5f, 0.5f, 1);
		
		ArrayList<AggCongestionCluster> congestionClusters = aggRegionCongestionClustering.getCongestionCluster();
		
		for (AggCongestionCluster congestionCluster : congestionClusters) {
			AggRegionCongestionCluster cc = (AggRegionCongestionCluster)congestionCluster;
			System.out.print("CluterID: " + cc.clusterID + ",");
			System.out.print(" StartTime: " + (cc.startTime) + ",");
			System.out.print(" EndtTime: " + (cc.endTime));
			//System.out.print(" MBR: " + cc.MBR);
			System.out.print(" Num: " + cc.num);
			System.out.println();
		}
		
		try {
			aggRegionCongestionClustering.getVIPCongestionClusterMap(2, 3);
		} catch (Exception e) {
			e.printStackTrace();
		}
	
	}

}
